package org.fujene.communicate.blockwise;

import java.io.IOException;
import java.util.TreeSet;
import java.util.logging.Logger;

import org.fujene.structure.Constants;
import org.fujene.structure.DataMatchingException;
import org.fujene.structure.NodeInfo;
import org.msgpack.MessagePackObject;
import org.msgpack.object.ArrayType;
import org.msgpack.object.IntegerType;
import org.msgpack.object.NilType;
import org.msgpack.object.RawType;
import org.msgpack.rpc.Request;

public class BlockwisePeerHandler {
	private Logger logger;
	private BlockwisePeerSystem parentSystem;

	public BlockwisePeerHandler(Logger logger, BlockwisePeerSystem parentSystem) {
		this.logger = logger;
		this.parentSystem = parentSystem;
	}

	/**
	 * ノードチェーンを送信します。
	 * 
	 * @param req
	 *            ... なし
	 */
	public void getNodeList(Request req) {
		MessagePackObject[] output = null;
		int count = 0;

		TreeSet<NodeInfo> currentInfos = new TreeSet<NodeInfo>(
				parentSystem.getNode());
		output = new MessagePackObject[2 * currentInfos.size()];

		for (NodeInfo eachPeer : currentInfos) {
			output[count++] = RawType.create(eachPeer.getAddr());
			output[count++] = IntegerType.create(eachPeer.getHash());
		}

		logger.finest("getNodeList - called.");

		req.sendResult(ArrayType.create(output));
	}
	
	/**
	 * ブロックの情報を取得します。
	 * 
	 * @param req ... 引数なし
	 */
	public void blockList(Request req) {
		int[] blockNums = parentSystem.getBlockList();
		MessagePackObject[] objs = new MessagePackObject[blockNums.length * 2];
		
		for (int i = 0; i < blockNums.length; i++) {
			objs[2*i] = IntegerType.create(blockNums[i] / 100);
			objs[2*i+1] = IntegerType.create(blockNums[i] % 100);
		}
		
		req.sendResult(ArrayType.create(objs));
	}

	/**
	 * 検索エンジンの情報各種を送信します。
	 * 
	 * @param req
	 *            ... なし
	 */
	public void getInfos(Request req) {
		MessagePackObject output = parentSystem.getInfo();

		logger.finest("getInfos - called.");

		req.sendResult(output);
	}

	/**
	 * ノードを登録します。
	 * 
	 * @param req
	 *            ... 0: (RawType/String)IPアドレス, 1: (IntegerType/int)本文チェーンハッシュ,
	 *            2: (IntegerType/int)辞書チェーンハッシュ
	 */
	public void register(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 2
				|| (!objs[0].isRawType() && !objs[1].isIntegerType())) {
			req.sendError(RawType
					.create("ERROR(register): Argument is invalid"));
			logger.severe("ERROR(register): Argument is invalid");
			return;
		}

		parentSystem.addReplication(new NodeInfo(objs[0].asString(), objs[1]
				.asLong()));

		logger.finest("register - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * ノードを削除します。
	 * 
	 * @param req
	 *            ... 0: (IntegerType/long)削除ハッシュ
	 */
	public void delete(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 1 || (!objs[0].isIntegerType())) {
			req.sendError(RawType.create("ERROR(delete): Argument is invalid"));
			logger.severe("ERROR(delete): Argument is invalid");
			return;
		}

		try {
			parentSystem.deleteReplication(new NodeInfo("Dummy", objs[0]
					.asInt()));
		} catch (DataMatchingException e) {
		} catch (IOException e) {
		}

		logger.finest("delete - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * IndexMemory の各関数に呼び出しをかけます。
	 * 
	 * @param req
	 *            ...0:(RawType/String)関数名, 1:(RawType/byte[])引数
	 */
	public void callGetFunc(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 2 || (!objs[0].isRawType() && !objs[1].isRawType())) {
			req.sendError(RawType
					.create("ERROR(callFunc): Argument is invalid"));
			logger.severe("ERROR(callFunc): Argument is invalid");
			return;
		}

		byte[] args = objs[1].asByteArray();

		byte[] returns = parentSystem.invokeGetFunc(objs[0].asString(), args);

		logger.finest("callGetFunc - " + objs[0].asString() + " - called.");

		req.sendResult(RawType.create(returns));
	}

	/**
	 * IndexMemory の各関数に呼び出しをかけます。
	 * 
	 * @param req
	 *            ...0:(RawType/String)関数名, 1:(RawType/byte[])引数
	 */
	public void callSetFunc(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 2 || (!objs[0].isRawType() && !objs[1].isRawType())) {
			req.sendError(RawType
					.create("ERROR(callFunc): Argument is invalid"));
			logger.severe("ERROR(callFunc): Argument is invalid");
			return;
		}

		byte[] args = objs[1].asByteArray();

		byte[] returns = parentSystem.invokeSetFunc(objs[0].asString(), args);

		logger.finest("callSetFunc - " + objs[0].asString() + " - called.");

		req.sendResult(RawType.create(returns));
	}

	/**
	 * メモリブロックを取得します。
	 * 
	 * @param req
	 *            ... 0:(IntegerType/int)ブロック番号 1:(IntegerType/int)ブロック種類
	 *            2:(IntegerType/int)本文種類
	 */
	public void getBlock(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 1 || (!objs[0].isIntegerType())) {
			req.sendError(RawType
					.create("ERROR(getBlock): Argument is invalid"));
			logger.severe("ERROR(getBlock): Argument is invalid");
			return;
		}
		int blockIndex = objs[0].asInt();
		
		MessagePackObject[] postobjs = new MessagePackObject[3];

		postobjs[0] = RawType.create(parentSystem.getBlock(blockIndex));
		int blockType = parentSystem.getBlockType(blockIndex);
		postobjs[1] = IntegerType.create(blockType);
		if (blockType == Constants.MEMORY_TYPE_CONTENTS
				|| blockType == Constants.MEMORY_TYPE_PURECONTENT)
			postobjs[2] = IntegerType.create(parentSystem.getContentType(blockIndex));
		else
			postobjs[2] = IntegerType.create(0);
		
		logger.finest("getBlock - called.");

		req.sendResult(ArrayType.create(postobjs));
	}

	/**
	 * メモリブロックを作成させます。
	 * 
	 * @param req
	 *            ... 0:(IntegerType/int)ブロック番号 1:(IntegerType/int)ブロック種類,
	 *            2:(IntegerType/int)本文種類 3:(BooleanType/boolean)実際にメモリ確保するかどうか
	 */
	public void setBlockInfo(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 4
				|| (!objs[0].isIntegerType() && !objs[1].isIntegerType()
						&& !objs[2].isIntegerType() && !objs[3].isBooleanType())) {
			req.sendError(RawType
					.create("ERROR(setBlockInfo): Argument is invalid"));
			logger.severe("ERROR(setBlockInfo): Argument is invalid");
			return;
		}

		parentSystem.pushBlock(objs[0].asInt(), objs[1].asInt(),
				objs[2].asInt(), objs[3].asBoolean());

		logger.finest("setBlockInfo - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * 生存しているかどうか返します。
	 * 
	 * @param req
	 *            ... 引数なし
	 */
	public void isLive(Request req) {
		logger.finest("isLive - called.");

		req.sendResult(RawType.create("Live"));
	}
}